package com.bytech.article.biz.service.article.Impl;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bytech.article.biz.mapper.articleTag.ArticleTagMapper;
import com.bytech.article.biz.mapper.articleThumb.ArticleThumbMapper;
import com.bytech.article.biz.mapper.tagToArticle.TagToArticleMapper;
import com.bytech.common.biz.entity.article.ArticleQuery;
import com.bytech.article.biz.mapper.article.ArticleMapper;
import com.bytech.article.biz.service.article.ArticleService;
import com.bytech.common.biz.entity.article.Article;
import com.bytech.common.biz.entity.article.ArticleDto;
import com.bytech.common.biz.entity.articleTag.ArticleTag;
import com.bytech.common.biz.entity.articleThumb.ArticleThumb;
import com.bytech.common.biz.entity.base.BaseResult;
import com.bytech.common.biz.entity.blogUser.BlogUser;
import com.bytech.common.biz.entity.tagToArticle.TagToArticle;
import com.bytech.common.biz.entity.timeline.TimeLineItem;
import com.bytech.common.biz.entity.timeline.TimeLineMonth;
import com.bytech.common.biz.entity.timeline.TimeLineYear;
import com.bytech.common.biz.enums.article.ArticleStatusEnum;
import com.bytech.common.biz.service.base.BasicServiceImpl;
import com.bytech.common.biz.service.remote.comment.RemoteCommentService;
import com.bytech.common.biz.service.remote.user.RemoteBlogUserService;
import com.bytech.common.biz.thirdparty.gallery.enums.PictureRatioEnum;
import com.bytech.common.biz.thirdparty.gallery.picture.GalleryPictureVo;
import com.bytech.common.biz.thirdparty.gallery.picture.query.PictureQuery;
import com.bytech.common.biz.thirdparty.gallery.service.GalleryApiClient;
import com.bytech.common.utils.ApplicationBeanUtils;
import com.bytech.common.utils.StringUtils;
import com.google.common.collect.Lists;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/**
 * @Author Zhao
 * @Create 2021-07-26
 */
@Service
public class ArticleServiceImpl extends BasicServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;
    @Resource
    private TagToArticleMapper tagToArticleMapper;
    @Resource
    private ArticleTagMapper articleTagMapper;
    @Resource
    private GalleryApiClient galleryApiClient;
    @Resource
    private RemoteBlogUserService remoteBlogUserService;
    @Resource
    private ArticleThumbMapper articleThumbMapper;
    @Resource
    private RemoteCommentService remoteCommentService;

    @Override
    public int getArticleCount() {
        LambdaQueryWrapper<Article> query = new LambdaQueryWrapper<>();
        query.eq(Article::getStatus, ArticleStatusEnum.PUBLISHED.getValue());
        return count(query);
    }

    @Override
    @Transactional
    public void deleteById(String id) {
        removeById(id);
        articleThumbMapper.delete(new LambdaQueryWrapper<ArticleThumb>().eq(ArticleThumb::getArticleId, id));
        remoteCommentService.deleteByArticleId(id);
        tagToArticleMapper.delete(new LambdaQueryWrapper<TagToArticle>().eq(TagToArticle::getArticleId, id));
    }

    @Override
    @Transactional
    public BaseResult<Integer> toggleThumb(String articleId, String userId) {
        Article article = articleMapper.selectById(articleId);
        if (article == null) {
            return BaseResult.<Integer>error("文章不存在").build();
        }
        BaseResult<BlogUser> userResult = remoteBlogUserService.getUserById(userId);
        if (userResult.getData() == null) {
            return BaseResult.<Integer>error("还未生成您的用户，在您第一次评论或留言后即会生成您的用户信息").build();
        }
        LambdaQueryWrapper<ArticleThumb> articleThumbQuery = new LambdaQueryWrapper<>();
        articleThumbQuery.eq(ArticleThumb::getArticleId, articleId).eq(ArticleThumb::getUserId, userId);
        Integer count = articleThumbMapper.selectCount(articleThumbQuery);
        int change = 0;
        if (count > 0) {
            articleThumbMapper.delete(articleThumbQuery);
            change = -count;
        } else {
            ArticleThumb newThumb = new ArticleThumb();
            newThumb.setArticleId(articleId);
            newThumb.setUserId(userId);
            newThumb.setCreateTime(new Date());
            articleThumbMapper.insert(newThumb);
            change = 1;
        }
        article.setThumbCount(article.getThumbCount() + change);
        articleMapper.updateById(article);
        return BaseResult.<Integer>success().msg("操作成功！").data(change).build();
    }

    @Override
    @Transactional
    public BaseResult<Article> saveArticle(ArticleDto articleDto) {
        BaseResult<Article> checkResult = checkArticle(articleDto);
        if (checkResult.isFailure()) {
            return checkResult;
        }
        Article dbArticle = getById(articleDto.getId());
        if (dbArticle == null) {
            articleDto.init();
            articleMapper.insert(articleDto);
        } else {
            articleMapper.updateById(articleDto);
        }
        // 删除重建标签关联关系
        LambdaQueryWrapper<TagToArticle> ttaDeleteQuery = new LambdaQueryWrapper<>();
        ttaDeleteQuery.eq(TagToArticle::getArticleId, articleDto.getId());
        tagToArticleMapper.delete(ttaDeleteQuery);

        if (!CollectionUtils.isEmpty(articleDto.getTagIds())) {
            List<TagToArticle> ttaList = Lists.newArrayList();
            articleDto.getTagIds().forEach(e -> {
                TagToArticle tta = new TagToArticle();
                tta.setId(StringUtils.getUUID32());
                tta.setArticleId(articleDto.getId());
                tta.setTagId(e);
                tta.setCreateTime(new Date());
                ttaList.add(tta);
            });
            tagToArticleMapper.batchInsert(ttaList);
        }
        return BaseResult.<Article>success("保存成功！").data(articleDto).build();
    }

    @Override
    public Page<ArticleDto> pageArticleByQuery(ArticleQuery<Article> articleQuery) {
        List<ArticleDto> pageResult = articleMapper.selectWithQuery(articleQuery);
        Page<ArticleDto> dtoPageResult = articleQuery.translateRecordsTo(ArticleDto.class);
        dtoPageResult.setRecords(pageResult);
        if (!CollectionUtils.isEmpty(dtoPageResult.getRecords())) {
            List<ArticleDto> records = dtoPageResult.getRecords();
            populateTags(records);
            PictureQuery picQuery = new PictureQuery();
            picQuery.setMinimalWidth(750);
            picQuery.setWhRatio(PictureRatioEnum.SIXTEEN_DIVIDE_NINE.getRatio());
            picQuery.setLimit(dtoPageResult.getRecords().size());
            List<GalleryPictureVo> pics = Optional.ofNullable(galleryApiClient.getRandomPictures(picQuery)).orElse(Lists.newArrayList());
            for (int i = 0; i < pics.size(); i++) {
                records.get(i).setPicture(pics.get(i));
            }
        }
        return dtoPageResult;
    }

    @Override
    public ArticleDto getArticleDtoByIdAndViewer(String articleId, String viewer) {
        ArticleDto articleDto = null;
        Article article = articleMapper.selectById(articleId);
        if (article != null) {
            articleDto = ApplicationBeanUtils.copyProperties(article, ArticleDto.class);
            List<ArticleTag> tagList = articleTagMapper.selectByArticleId(articleId);
            articleDto.setTags(tagList);

            LambdaQueryWrapper<ArticleThumb> articleThumbQuery = new LambdaQueryWrapper<>();
            articleThumbQuery.eq(ArticleThumb::getArticleId, articleId).eq(ArticleThumb::getUserId, viewer);
            Integer viewerThumbCount = articleThumbMapper.selectCount(articleThumbQuery);
            if (viewerThumbCount > 0) {
                articleDto.setThumbed(true);
            }

            BaseResult<Integer> commentCountResult = remoteCommentService.countByArticle(articleId);
            articleDto.setCommentCount(Optional.ofNullable(commentCountResult.getData()).orElse(0));
        }
        return articleDto;
    }

    @Override
    public void populateTags(List<ArticleDto> articleDtoList) {
        Set<String> articleIds = articleDtoList.stream().map(ArticleDto::getId).collect(Collectors.toSet());
        LambdaQueryWrapper<TagToArticle> ttaQuery = new LambdaQueryWrapper<>();
        ttaQuery.in(TagToArticle::getArticleId, articleIds);
        List<TagToArticle> ttaList = tagToArticleMapper.selectList(ttaQuery);
        if (!CollectionUtils.isEmpty(ttaList)) {
            List<Long> tagIds = ttaList.stream().map(TagToArticle::getTagId).collect(Collectors.toList());
            Optional.of(articleTagMapper.selectList(new LambdaQueryWrapper<ArticleTag>().in(ArticleTag::getId, tagIds))).ifPresent(articleTags -> {
                articleDtoList.forEach(article -> {
                    List<Long> curTagIds = ttaList.stream().filter(tta -> tta.getArticleId().equals(article.getId())).map(TagToArticle::getTagId).collect(Collectors.toList());
                    article.setTags(articleTags.stream().filter(e -> curTagIds.contains(e.getId())).collect(Collectors.toList()));
                });
            });
        }
    }

    @Override
    public List<TimeLineYear> buildTimeLine() {
        List<Article> articleList = Optional.ofNullable(articleMapper.selectList(new LambdaQueryWrapper<Article>().eq(Article::getStatus, ArticleStatusEnum.PUBLISHED.getValue())))
                .orElse(Lists.newArrayList());
        List<ArticleDto> articleDtoList = ApplicationBeanUtils.copyProperties(articleList, ArticleDto.class);
        populateTags(articleDtoList);
        List<TimeLineItem> timeLineItemList = articleDtoList.stream().map(TimeLineItem::new).collect(Collectors.toList());

        // 聚合
        Map<Integer, Map<Integer, List<TimeLineItem>>> yearMap = timeLineItemList.stream()
                .collect(Collectors.groupingBy(TimeLineItem::getYear, Collectors.mapping(e -> e, Collectors.groupingBy(TimeLineItem::getMonth))));

        // 组装
        List<Integer> yearList = Lists.newArrayList(yearMap.keySet());
        yearList.sort((o1, o2) -> o2.compareTo(o1)); // 年份倒序
        List<TimeLineYear> timeLineYearList = Lists.newArrayList();
        for (Integer year : yearList) {
            TimeLineYear timeLineYear = new TimeLineYear();
            Map<Integer, List<TimeLineItem>> monthMap = yearMap.get(year);
            List<Integer> monthList = Lists.newArrayList(monthMap.keySet());
            monthList.sort((o1, o2) -> o2.compareTo(o1)); // 月份倒序
            List<TimeLineMonth> timeLineMonthList = Lists.newArrayList();
            for (Integer month : monthList) {
                List<TimeLineItem> monthItemList = monthMap.get(month);
                monthItemList.sort((o1, o2) -> o2.getTime().compareTo(o1.getTime())); // 时间倒序
                TimeLineMonth timeLineMonth = new TimeLineMonth();
                timeLineMonth.setItemList(monthMap.get(month));
                timeLineMonth.setMonth(month);
                timeLineMonthList.add(timeLineMonth);
            }
            timeLineYear.setMonthList(timeLineMonthList);
            timeLineYear.setYear(year);
            timeLineYearList.add(timeLineYear);
        }
        return timeLineYearList;
    }

    private LambdaQueryWrapper<Article> buildArticleQuery(ArticleQuery<Article> articleQuery) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        if (!StringUtils.isBlank(articleQuery.getTitle())) {
            queryWrapper.like(Article::getTitle, articleQuery.getTitle());
        }
        if (!StringUtils.isBlank(articleQuery.getStatus())) {
            queryWrapper.eq(Article::getStatus, articleQuery.getStatus());
        }
        return queryWrapper;
    }

    private BaseResult<Article> checkArticle(ArticleDto article) {
        if (StringUtils.isBlank(article.getTitle())) {
            return BaseResult.<Article>error("请设置文章标题").build();
        }
        if (!article.isOriginal() && StringUtils.isBlank(article.getOriginalUrl())) {
            return BaseResult.<Article>error("设置非原创文章的必须填写原文链接").build();
        }
        if (article.getIsIndex() == null) {
            article.setIsIndex(false);
        }
        if (article.getIsTop() == null) {
            article.setIsTop(false);
        }
        if (article.getNeedPassword() == null) {
            article.setNeedPassword(false);
        }
        if (article.getIsOriginal() == null) {
            article.setIsOriginal(false);
        }
        return BaseResult.<Article>success().build();
    }
}
